/*
  Ejemplo de programa servidor. Atiende peticiones aritmeticas, el protocolo de transporte
  sera el comm::Transport y el contenido del mensaje sera el resutaldo de un comm::Codec con los
  (x, y, op) -> El resultado sera estos tres componente mas result.
  
  Ejemplo de uso del sistema de receiveres, que son capaces de tratar N peticiones de forma
  totalmetne simultanea en caso de estar en un entorno MT.

  Para poder probar el sistema de congestion se puede indicar un numero de milisegundos de 
  retardo aplicados a cada contestacion.

  Los clientes pueden ser: client.p o kclient.p
  
  Ejemplo de implementacion usando el sistema iRS de NemesisRD.comm 1.5.2
*/
#include <iostream>

#include <nemesis.h>
#include <nemesis.comm.h>

#include <nemesis.xml.Node.h>
#include <nemesis.xml.Attribute.h>

#include <nemesis.app.functions.h>

#include <test.Request.h>
#include <test.Response.h>
#include <test.Communicator.h>

using namespace std;
using namespace test;

class MyCommunicator : public test::Communicator {
public:
   MyCommunicator (Communicator::WorkMode::_v workMode) :  test::Communicator (workMode) {;}
};

class MyReceiver : public Receiver {
public:
   static const char* className () throw () { return "MyReceiver"; }
   
private:
   Request a_request;
   Response a_response;
   MyCommunicator* a_communicator;
   
   MyReceiver () : Receiver ("MyReceiver") { ; }
   void initialize () throw (RuntimeException);
   void apply (comm::ClientSocket &, const Message&) throw (RuntimeException);
   
   friend class Allocator <MyReceiver>;      
};

class ArithmeticServer : public comm::Application {
public:
   ArithmeticServer ();
      
private:
   MyCommunicator* a_communicator;
   ReceiverFactoryImpl <MyReceiver> a_receiverFactory;
   comm::ServerSocket* a_serverSocket;

   void initialize () throw (RuntimeException);
   void run () throw (RuntimeException);
   xml::Node* asXML (xml::Node* app) const throw ();
};

using namespace std;
using namespace nemesis::comm;

int main (int argc, const char** argv)
{
   CommandLine& commandLine (CommandLine::instanciate ());
   ArithmeticServer app;

   srand (time (NULL));
   
   try {
      commandLine.initialize (argv, argc);
      commandLine.verify ();

      Logger::setLevel (Logger::Debug); 
      string traceFile ("server.");
      traceFile += nemesis::functions::asString ((int) getpid ());
      traceFile += ".trace";      
      Logger::initialize ("arithmeticServer", new TraceWriter (traceFile.c_str (),4096000));
 
      app.start ();
   }
   catch (Exception& ex) {
      cout << ex.asString () << endl;
   }
   
   return 0;
}

ArithmeticServer::ArithmeticServer () : 
   Application ("arithmeticServer", "Servidor de operaciones (iRS)", "1.0"),
   a_communicator (NULL)
{
   CommandLine& commandLine (CommandLine::instanciate ());
      
   commandLine.add ("p", CommandLine::Argument::Mandatory, "Puerto en el que atender peticiones");
   commandLine.add ("a", CommandLine::Argument::Mandatory, "Direccin IP en la que atender");
   commandLine.add ("d", CommandLine::Argument::Mandatory, "Retardo aplicado a la contestacio");
   commandLine.add ("limit", CommandLine::Argument::Mandatory, "% de ocupacion que permitimos");
   commandLine.add ("r", CommandLine::Argument::Optional, "Indicador de reuso de direccin", false);
   commandLine.add ("n", CommandLine::Argument::Optional, "Numero de mensajes a servir", true);
   commandLine.add ("trace", CommandLine::Argument::Optional, "Nivel de trazas (debug,warning, error,...)");
   commandLine.add ("clone", CommandLine::Argument::Optional, "Aplica el metodo de clonado en el tratamiento de mensajes", false);
}

//-----------------------------------------------------------------------------------------
// Inicializa el servidor de sockets.
//-----------------------------------------------------------------------------------------
void ArithmeticServer::initialize () 
   throw (RuntimeException)
{
   CommandLine& cl (CommandLine::instanciate ());

   int port = cl.getIntegerValue ("p");
   const comm::Device* device = Network::instanciate ().find (Device::asAddress (cl.getValue ("a")));

   a_serverSocket = new ServerSocket (INetAddress (device, port), cl.exists ("r"));
   a_serverSocket->setCategory (777);
   a_serverSocket->setReceiverFactory (a_receiverFactory);

   comm::Communicator::WorkMode::_v workMode = (cl.exists ("clone")) ? comm::Communicator::WorkMode::Clone: comm::Communicator::WorkMode::Single;

   a_communicator = new MyCommunicator (workMode);
}

//-----------------------------------------------------------------------------------------
// Atiende las peticiones.
// Cuando hay un nuevo mensaje invocar�a Communicator::eventReceiveMessage
//-----------------------------------------------------------------------------------------
void ArithmeticServer::run ()
   throw (RuntimeException)
{
   CommandLine& cl (CommandLine::instanciate ());

   a_communicator->attach (a_serverSocket);
   a_communicator->setDelay (cl.getIntegerValue ("d"));

   if (cl.exists ("n") == true)
      a_communicator->setMaxMessage (cl.getIntegerValue ("n"));

   if (cl.exists ("trace"))
      Logger::setLevel (Logger::asLevel (cl.getValue ("trace")));

   CongestionController::instanciate ().setLimit (cl.getIntegerValue ("limit"));

   a_communicator->accept ();
}

xml::Node* ArithmeticServer::asXML (xml::Node* app) const 
   throw ()
{
   xml::Node* node = app::Application::asXML (app);
   
   node->createAttribute ("MaxMessage", a_communicator->getMaxMessage ());
   node->createAttribute ("Message", a_communicator->getMessage ());
   
   return node;
}

void MyReceiver::initialize ()
   throw (RuntimeException)
{
   a_communicator = app::functions::component <MyCommunicator> (FILE_LOCATION);
}

void MyReceiver::apply (ClientSocket& clientSocket, const Message& message) 
   throw (RuntimeException)
{
   LOGMETHOD (TraceMethod tm ("MyReceiver", "apply", FILE_LOCATION));

   if (a_communicator->canContinue (clientSocket) == false)
      return;      

   a_request.decode (message.getBody ());

   a_communicator->delay ();

   a_response.x = a_request.x;
   a_response.y = a_request.y;
   a_response.initTime = a_request.initTime;

   switch (a_response.op = a_request.op) {
      case '+':
         a_response.result = a_request.x + a_request.y;
         break;
      case '-':
         a_response.result = a_request.x - a_request.y;
         break;
      case '*':
         a_response.result = a_request.x * a_request.y;
         break;
      case '/':
         a_response.result = (a_request.y != 0) ? (a_request.x / a_request.y): 0;
         break;
   }

   LOGINFORMATION (
      string msg = nemesis::functions::asString ("%d %c %d = %d", a_request.x, a_request.op, a_request.y, a_response.result);
      msg += nemesis::functions::asText (" | InitTime: ",  a_response.initTime);
      Logger::information (msg, FILE_LOCATION);
   )

   try {
      clientSocket.send (a_response);
   }
   catch (Exception& ex) {
      ex.trace ();
   }
}
